課程:Redux 思維與 RTK 核心 第 4 堂:React 元件整合 Redux
20Lesson 4 note
本堂課深入學習如何在 React 元件中與 Redux Store 進行通訊,涵蓋了資料的讀取(useSelector)、指令的發送(useDispatch),以及如何建立 TypeScript 型別安全的自訂 Hooks,最終整合出一個完整的 Counter App。
useSelector 深入解析
useSelector是 React 與 Redux 之間的「訂閱機制」,讓元件能從全域 State 中選取特定資料。- 運作機制與 re-render 原理:
- 每當 Store 變更時,
useSelector會重新執行 Selector 函數並獲取結果。
- 每當 Store 變更時,
- 預設使用 嚴格相等比較 (===):將新結果與上一次結果對比,若不同則觸發元件重新渲染。
- 效能陷阱:回傳新物件/陣列:
- 在 Selector 內寫
state => ({ value: state.counter.value })會導致元件在每次 Store 變動時都重繪,因為物件字面量{}每次都會產生新的引用位址。
- 在 Selector 內寫
- 最佳實踐:
- 拆分讀取:多次呼叫
useSelector選取原始型別(number, string 等)。
- 拆分讀取:多次呼叫
- 淺比較:若必須回傳物件,可搭配
shallowEqual作為第二個參數。 - 集中管理:建議將 Selector 函數定義在 Slice 檔案中以便重用。
useDispatch 發送 Action
useDispatch扮演「傳令兵」角色,負責將元件的意圖(Action)傳遞給 Reducer。- 正確呼叫方式:
- 必須呼叫 Action Creator 的執行結果,例如
dispatch(increment())而非僅傳入函數名。
- 必須呼叫 Action Creator 的執行結果,例如
- Payload 的傳遞:
- Action Creator 接受參數後,RTK 會自動將其封裝成
action.payload,讓 Reducer 能存取 UI 傳入的具體數值。
- Action Creator 接受參數後,RTK 會自動將其封裝成
- 單向資料流閉環:
- 觸發事件 -> Dispatch Action -> Reducer 計算新 State -> Store 更新 -> Selector 偵測變化 -> UI 重新渲染。
dispatch函數在元件生命週期中是穩定的,放入useEffect的依賴陣列中不會引發額外的執行。
型別安全 Typed Hooks
- 建立自訂 Hooks 的必要性:
- 原始的
useSelector不具備 App 的 State 結構資訊,導致開發時缺乏自動補完。
- 原始的
useAppSelector與useAppDispatch能提供「一次設定,終身受益」的開發體驗。- 核心型別推導:
RootState:透過ReturnType<typeof store.getState>自動推導全域狀態結構。
AppDispatch:透過typeof store.dispatch捕捉包含 Middleware 能力的 Dispatch 型別。- 解決循環依賴:
- 在
hooks.ts中使用import type引入 Store 型別。這在編譯後會被移除,確保檔案間只有型別連結而無執行期依賴。
- 在
Counter App 完整整合
- 專案結構慣例:採用 Feature-based logic,將
store.ts與hooks.ts放在app/目錄,各功能邏輯放在features/。 - Local State 與 Global State 的邊界:
- 輸入框中的「過程資料」(正在輸入的字串)應保留在元件內部的
useState。
- 輸入框中的「過程資料」(正在輸入的字串)應保留在元件內部的
- 只有在使用者確認送出(點擊按鈕)時,才將結果
dispatch給 Redux。 - 開發者工具驗證:
- 透過 Redux DevTools 觀察 Action 序列(例如
counter/increment)與 Payload 內容,利用「時光旅行」功能調試狀態。
- 透過 Redux DevTools 觀察 Action 序列(例如
Q&A
- Q: 假設我在元件寫
const productCount = useAppSelector(state => ({ count: state.products.items.length })),當我刪除一個完全不相關的「使用者通知」時,該元件會重新渲染嗎?- 元件 會重新渲染。這是關鍵的效能細節,原因在於物件字面量
{}的性質。
- 元件 會重新渲染。這是關鍵的效能細節,原因在於物件字面量
- 雖然
length的數值沒變,但每次執行 Selector 產生的新物件在記憶體位址上與前一個不同。 useSelector的嚴格相等比較(===)會判定為「資料已變更」,進而強制觸發重新渲染。- Q: 在
dispatch(toggleTodo(5))中,toggleTodo的角色是什麼?而數字5在 Reducer 內部會如何被存取?toggleTodo是一個 Action Creator (Action 生成器)。它是一個工廠函數,負責製造出符合 Redux 規範的 Action 物件。
- 數字
5是 Payload (負載)。在 Reducer 內部,它是透過action.payload被存取的。RTK 會自動將傳入生成器的參數包裝進payload屬性中。 - Q: 為什麼要特地封裝
useAppSelector?它內部「記住」了什麼型別?- 因為原始的
useSelector是通用泛型,它不知道具體的 Store 地圖長什麼樣子。若直接使用,每次都必須手動標註(state: RootState),既冗餘又容易出錯。
- 因為原始的
useAppSelector內部預先綁定了**RootState**型別。這讓開發者在元件內輸入state.時,VSCode 能精準彈出所有 Slice(如counter、auth)的自動補全建議。